package com.fulihui.information.biz.content.impl.handler;


import com.fulihui.information.biz.content.CategoryBizManager;
import com.fulihui.information.common.config.AppConst;
import com.fulihui.information.core.repository.CategoryRepository;
import com.fulihui.information.core.repository.ContentRepository;
import com.fulihui.information.dal.dataobj.InfoContentExample;
import com.fulihui.information.dto.CategoryDTO;
import com.fulihui.information.dto.ContentListItemDTO;
import com.fulihui.information.enums.EContentStatus;
import com.fulihui.information.enums.EDataObtainType;
import com.fulihui.information.request.ContentPageQueryForAppRequest;
import org.apache.dubbo.common.utils.LRUCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.Collections.emptyList;
import static org.near.toolkit.common.StringUtil.isNotBlank;
import static org.springframework.util.CollectionUtils.isEmpty;

/**
 * MySQL数据库实现的分页按第一页第一条的时间作为后续分页基准，新数据不会被计算，第二页也会过滤掉第一条数据相同秒的数据，
 * 从上一秒开始计算，过滤的数据要追加到rows上，然后查询后在去掉。数据排序方式为有序排序。
 *
 * @author Willard.Hu on 2017/1/3 0003.
 */
@Component("mySQLFirstLimitedDateOrderlyAppListPageObtainHandler")
public class MySQLFirstLimitedDateOrderlyAppListPageObtainHandler extends AbstractAppListPageObtainHandler {
    /**
     * 线程缓存获取集合时计算的数据总量的key
     */
    private static final String FIRST_LIMITED_DATE_ORDERLY_FILTER_COUNT = "FIRST_LIMITED_DATE_ORDERLY_FILTER_COUNT";

    // 2分钟的毫秒数
    private static final long TWO_MINUTES = 2 * 60 * 1000;

    @Autowired
    private ContentRepository  contentRepository;
    @Autowired
    private CategoryRepository categoryRepository;
    @Autowired
    private CategoryBizManager categoryBizManager;

    private LRUCache<String, ContentListCache> contentListLruCache;

    public MySQLFirstLimitedDateOrderlyAppListPageObtainHandler() {
        contentListLruCache = new LRUCache<>();
    }

    @Override
    List<ContentListItemDTO> queryPageList(ContentPageQueryForAppRequest request) {
        String lruCacheKey = request.getCategoryCode() + "_" + request.getPage();
        ContentListCache cache = contentListLruCache.get(lruCacheKey);
        // 没有缓存或者缓存时效已大于2分钟重新获取数据
        if (cache == null || System.currentTimeMillis() > cache.createTimestamp + TWO_MINUTES
                // 第一页如果先缓存失效，后面几页如果还在使用缓存，会导致数据重复，
                // 第一页第一个需过滤的时间如果变化了，页需要重新刷新缓存
                || request.getPage() > 1 && !request.getLastQueries().get(0).equals(cache.list.get(0).getOrdid())) {
            log.info("{}_{} -> 重新初始化缓存", request.getCategoryCode(), request.getPage());
            Date limitDate = toLimitDate(request.getLastQueries());

            // XXX 查询出的数据已不包含第一条相同时间的数据，后续没问题的话删除
            //int filterCount = filterCount(request.getCategoryCode(), limitDate);
            //ThreadContext.getInstance().setAttribute(FIRST_LIMITED_DATE_ORDERLY_FILTER_COUNT, filterCount);

            InfoContentExample example = toBasicExample(request.getCategoryCode(), limitDate);
            example.setOrderByClause("gmt_create desc, id desc");
            List<ContentListItemDTO> list = contentRepository.queryItemListPage(request.start4Mysql(),
                    request.getRows() /*+ filterCount*/, example);
            if (isEmpty(list)) { // 用于添加置顶，需要可修改的集合
                return new ArrayList<>(1);
            }
            // XXX 查询出的数据已不包含第一条相同时间的数据，后续没问题的话删除
            /*if (filterCount > 0) {
                list = list.subList(filterCount, list.size());
            }*/
            String lastQuery = request.getPage() == 1 ?
                    String.valueOf(list.get(0).getGmtCreate()) : request.getLastQueries().get(0);
            list.forEach(it -> {
                it.setRedisId(lastQuery); // 向下兼容处理
                it.setOrdid(lastQuery);
            });
            cache = new ContentListCache(list, System.currentTimeMillis());
            contentListLruCache.put(lruCacheKey, cache);
        }
        // 深拷贝列表存入缓存，防止集合在外部被其他方法修改导致错误
        return new ArrayList<>(cache.list);
    }

    @Override
    int queryTotalCount(ContentPageQueryForAppRequest request) {
        /*Date limitDate = toLimitDate(request.getLastQueries());
        ThreadContext threadContext = ThreadContext.getInstance();
        Integer filterCount = (Integer) threadContext.getAttribute(FIRST_LIMITED_DATE_ORDERLY_FILTER_COUNT);
        if (filterCount == null) { // 从线程变量中获取，没有找到，重新计算
            filterCount = filterCount(request.getCategoryCode(), limitDate);
        } else { // 从线程变量中删除
            threadContext.removeAttribute(FIRST_LIMITED_DATE_ORDERLY_FILTER_COUNT);
        }
        InfoContentExample example = toBasicExample(request.getCategoryCode(), limitDate);
        long totalCount = contentRepository.queryItemListCount(example);
        return (int) (totalCount - filterCount);*/

        // 默认最大值，节约性能，APP分页无需知道总数据书和页码，只要下一页无返回收据就能认为没有下一页了
        return Integer.MAX_VALUE;
    }

    @Override
    public EDataObtainType obtainType() {
        return EDataObtainType.FIRST_LIMITED_DATE_ORDERLY;
    }

    /**
     * 获取需要过滤的行数，第二页会将与第一页第一条时间相同秒数的数据全部过滤掉
     *
     * @param categoryCode 类目代码
     * @param limitDate    限制时间
     * @return 要过滤的数量
     */
    private int filterCount(String categoryCode, Date limitDate) {
        if (limitDate == null) {
            return 0;
        }
        InfoContentExample example = toBasicExample(categoryCode, null);
        // toBasicExample()方法中穿件了Criteria，所以这里假设getOredCriteria()一定有一个值存在
        // 后续修改需要关注这段逻辑，如果没有创建Criteria，则会导致NPE
        example.getOredCriteria().get(0).andGmtCreateEqualTo(limitDate);
        return contentRepository.queryItemListCount(example).intValue();
    }

    /**
     * 转换上层传入的第一页第一条数据的时间值，unix时间戳，第一页为空
     *
     * @param lastQueries lastQuery
     * @return Date
     */
    private Date toLimitDate(List<String> lastQueries) {
        return !isEmpty(lastQueries) && isNotBlank(lastQueries.get(0)) ?
                new Date(Long.parseLong(lastQueries.get(0)) * 1000) : null;
    }

    /**
     * 类目代码，限制时间转换基础的 InfoContentExample 对象
     *
     * @param categoryCode 类目代码
     * @param limitDate    限制时间
     * @return {@link InfoContentExample}
     */
    protected InfoContentExample toBasicExample(String categoryCode, Date limitDate) {
        InfoContentExample example = new InfoContentExample();
        InfoContentExample.Criteria criteria = example.createCriteria()
                .andStatusEqualTo(EContentStatus.CHECK.getCode())
                .andTopStatusEqualTo("0"); // 置顶数据直接插入第一页第一条，后续分页无需再展示
        // 获取类目代码查询条件
        if (AppConst.CONTENT_HOTS_CATEGORY_CODE.equals(categoryCode)) { // 热门
            criteria.andCategoryCodeIn(hotsCategorys());
        } else { // 其他
            criteria.andCategoryCodeEqualTo(categoryCode);
        }
        // 添加时间限制查询条件
        if (limitDate != null) {
            criteria.andGmtCreateLessThan(limitDate);
        }
        return example;
    }

    class ContentListCache {
        List<ContentListItemDTO> list;
        long                     createTimestamp;

        ContentListCache(List<ContentListItemDTO> list, long createTimestamp) {
            this.list = new ArrayList<>(list); // 深拷贝列表存入缓存，防止集合在外部被其他方法修改导致错误
            this.createTimestamp = createTimestamp;
        }
    }

    /**
     * 类目有Redis缓存，没5分钟一次失效，重新从数据库中加载
     */
    private List<String> hotsCategorys() {
        List<CategoryDTO> list = categoryBizManager.queryGroup("YOUKANTOU", null);
        if (isEmpty(list)) {
            return emptyList();
        }
        return list.parallelStream()
                .map(CategoryDTO::getCode)
                .filter(it -> !it.equals(AppConst.CONTENT_ESSAY_JOKE_CATEGORY_CODE))
                .collect(Collectors.toList());
    }
}
